﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Db4objects.Db4o;

namespace twilters
{
    
    /** Datastore implementation that uses Db4o for .NET Object Database Library
     * more about db4o from www.db4o.com
     */
    public class DataStoreDb4o : DataStore
    {
        IObjectContainer database;

        public List<Filter> findMyFilters(String ownerUsername)
        {
            try
            {
                IList<Filter> found = database.Query<Filter>(delegate(Filter filter)
                {
                    return filter.OwnerUsername.Equals(ownerUsername);
                });
                return found.ToList<Filter>();
            }
            catch (Exception exc)
            {
                throw new DataStoreException("Could not query filter data: " + exc.Message, exc);
            }
        }

        public void storeTweets(List<Tweet> tweets)
        {
            try
            {
                foreach (Tweet tweet in tweets)
                {
                    storeTweet(tweet);
                }
            }
            catch (Exception exc)
            {
                throw new DataStoreException("Could not store tweets", exc);
            }
        }

        public List<Tweet> loadFilteredTweets(Filter filter, String followerUsername)
        {
            IList<Tweet> tweets = database.Query<Tweet>(delegate(Tweet tweet)
            {
                // if no filter, return everything
                if (filter == null)
                {
                    return true;
                }
                if (!tweet.FollowerUsername.Equals(followerUsername))
                {
                    return false;
                }
                List<FilterParameter> parameters = filter.GetParameters();
                if (parameters.Count() < 1)
                {
                    // if no filtering parameters, then let it pass
                    return true;
                }
                int matchCount = 0;
                foreach (FilterParameter parameter in parameters)
                {
                    switch (parameter.GetMatchType())
                    {
                        case MatchType.Exact:
                            if (tweet.GetFieldValue(parameter.Field).Equals(parameter.Value))
                            {
                                matchCount++;
                            }
                            break;
                        case MatchType.Contains:
                            if (tweet.GetFieldValue(parameter.Field) is String && parameter.Value is String)
                            {
                                String str1 = (String)tweet.GetFieldValue(parameter.Field);
                                String str2 = (String)parameter.Value;
                                if (str1.Contains(str2))
                                {
                                    matchCount++;
                                }
                            }
                            break;
                    }
                }
                // all parameters must match per tweet
                //return matchCount == parameters.Count();
                return matchCount > 0;
            });
            List<Tweet> found = tweets.ToList<Tweet>();
            found.Sort(delegate(Tweet t1, Tweet t2) { return -t1.CreateDate.CompareTo(t2.CreateDate); });
            return found;
        }

        public void storeFilter(Filter filter)
        {
            IList<Filter> found = database.Query<Filter>(delegate(Filter foundFilter)
            {
                return foundFilter.FilterID == filter.FilterID;
            });
            if (found.Count() > 0)
            {
                database.Delete(found.ElementAt(0));
            }
            storeData(filter);
        }

        public void deleteFilter(Filter filter)
        {
            database.Delete(filter);
        }

        public void purgeFilters()
        {
            purgeObjects<Filter>();
        }

        public void storeTweet(Tweet tweet)
        {
            // store only not cached tweets
            Tweet example = new Tweet();
            example.Id = tweet.Id;
            if (database.QueryByExample(example).Size() < 1)
            {
                storeData(tweet);
            }
        }

        public void purgeTweetCache()
        {
            purgeObjects<Tweet>();
        }

        public int NextId()
        {
            try
            {
                IList<IDSequence> seqs = database.Query<IDSequence>(typeof(IDSequence));
                int nextId = 1;
                if (seqs.Count() > 0)
                {
                    nextId = ((int)seqs.ElementAt(0).NextId) + 1;
                }
                IDSequence seq = new IDSequence();
                seq.NextId = nextId;
                storeSingleObject<IDSequence>(seq);
                return nextId;
            }
            catch (Exception exc)
            {
                throw new DataStoreException("Error generating ID: " + exc.Message, exc);
            }
        }

        // Its public for testing purposes  at the moment
        public void resetIDSequence()
        {
            try
            {
                purgeObjects<IDSequence>();
            }
            catch (Exception exc)
            {
                throw new DataStoreException("Could not reset sequence: " + exc.Message, exc);
            }
        }

        public void Open(ConfigurationHandler configuration)
        {
            try
            {
                database = Db4oFactory.OpenFile(configuration.getUserDataFile());
            }
            catch (Exception exc)
            {
                throw new DataStoreException("Could not open database: " + exc.Message, exc);
            }
        }

        public void Close()
        {
            try
            {
                database.Close();
            }
            catch (NullReferenceException nre)
            {
                //do nothing or log this situation somehow?
            }
        }

        public void storePrivateData(PrivateData privateData)
        {
            storeSingleObject<PrivateData>(privateData);
        }

        public PrivateData loadPrivateData()
        {
            PrivateData privateData = (PrivateData)loadSingleObject<PrivateData>();

            if (privateData == null)
            {
                privateData = new PrivateData();
            }
            return privateData;
        }

        #region private
        private void purgeObjects<T>()
        {
            try
            {
                IList<T> objects = database.Query<T>(typeof(T));
                foreach (T obj in objects)
                {
                    database.Delete(obj);
                }
            }
            catch (Exception exc)
            {
                throw new DataStoreException("Could not purge object of type " + typeof(T).Name + ": " + exc.Message, exc);
            }
        }

        private void storeData(Object obj)
        {
            try
            {
                database.Store(obj);
            }
            catch (Exception exc)
            {
                throw new DataStoreException("Could not store object of type " + obj.GetType() + ": " + exc.Message, exc);
            }
        }

        private object loadSingleObject<T>()
        {
            try
            {
                IList<T> objects = database.Query<T>(typeof(T));
                if (objects.Count() > 0)
                {
                    return objects.ElementAt(0);
                }
                return null;
            }
            catch (Exception exc)
            {
                throw new DataStoreException("Could not load object of type " + typeof(T).ToString() + ": " + exc.Message, exc);
            }
        }

        private void storeSingleObject<T>(Object o)
        {
            purgeObjects<T>();
            storeData(o);
        }
        #endregion private
    }
}
